package com.ruicar.afs.cloud.gateway.filter.log;

import cn.hutool.core.lang.UUID;
import com.ruicar.afs.cloud.common.core.constant.CommonConstants;
import com.ruicar.afs.cloud.common.core.log.util.LogUtils;
import com.ruicar.afs.cloud.common.core.util.IpUtils;
import com.ruicar.afs.cloud.common.core.util.RequestLogHelper;
import io.netty.buffer.UnpooledByteBufAllocator;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.reactivestreams.Publisher;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

import static com.ruicar.afs.cloud.common.core.constant.CommonConstants.AFS_TRACKER_ID;
import static org.springframework.cloud.gateway.filter.NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER;

/**
 * The type ResponseLogFilter
 * <p>Description: </p>
 *
 * @author Fzero
 * @version 1.0
 * @date Created on 2020.06.10 14:17:57
 * @since 1.0
 */
@Slf4j(topic = "afs-gateway-response")
@Component
@AllArgsConstructor
public class ResponseLogFilter implements GlobalFilter, Ordered {
    private final Environment environment;
    /**
     * Gets order *
     *
     * @return the order
     */
    @Override
    public int getOrder() {
        return WRITE_RESPONSE_FILTER_ORDER-1;
    }

    /**
     * Filter mono
     *
     * @param exchange exchange
     * @param chain    chain
     * @return the mono
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        try {
            ServerHttpRequest request = exchange.getRequest();
            ServerRequest serverRequest = ServerRequest.create(exchange,
                    HandlerStrategies.withDefaults().messageReaders());
            URI requestUri = request.getURI();
            String uriQuery = requestUri.getQuery();
            HttpHeaders headers = request.getHeaders();
            MediaType mediaType = headers.getContentType();
            String schema = requestUri.getScheme();
            String method = request.getMethodValue().toUpperCase();

            // 只记录http、https请求
            if ((!"http".equals(schema) && !"https".equals(schema))) {
                return chain.filter(exchange);
            }
            final AtomicReference<String> requestBody = new AtomicReference<>();// 原始请求体
            // 排除流文件类型,比如上传的文件contentType.contains("multipart/form-data")
            if (Objects.nonNull(mediaType) && RequestLogHelper.isUploadFile(mediaType)) {
                requestBody.set("上传文件");
                return chain.filter(exchange);
            } else {
                if (method.equals("GET")) {
                    if (StringUtils.isNotBlank(uriQuery)) {
                        requestBody.set(uriQuery);
                    }
                } else if (headers.getContentLength() > 0&&headers.getContentLength()<262144){
                    return serverRequest.bodyToMono(String.class).flatMap(reqBody -> {
                        requestBody.set(reqBody);
                        // 重写原始请求
                        ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
                            @Override
                            public HttpHeaders getHeaders() {
                                HttpHeaders httpHeaders = new HttpHeaders();
                                httpHeaders.putAll(super.getHeaders());
                                return httpHeaders;
                            }

                            @Override
                            public Flux<DataBuffer> getBody() {
                                NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
                                DataBuffer bodyDataBuffer = nettyDataBufferFactory.wrap(reqBody.getBytes());
                                return Flux.just(bodyDataBuffer);
                            }
                        };
                        ServerHttpResponseDecorator responseDecorator = getServerHttpResponseDecorator(exchange,
                                requestBody);
                        return chain.filter(exchange.mutate()
                                .request(requestDecorator)
                                .response(responseDecorator)
                                .build());
                    });
                }else if (headers.getContentLength()>=262144){
                    requestBody.set("请求体过大");
                }
                if(exchange.getResponse().getHeaders().getContentLength()>=262144){
                    log.info("响应体过大，不做记录");
                    return chain.filter(exchange);
                }
                ServerHttpResponseDecorator decoratedResponse = getServerHttpResponseDecorator(exchange,requestBody);

                return chain.filter(exchange.mutate().response(decoratedResponse).build());
            }

        } catch (Exception e) {
            log.error("请求响应日志打印出现异常", e);
            return chain.filter(exchange);
        }

    }

    /**
     * Gets server http response decorator *
     *
     * @param exchange    exchange
     * @param requestBody request body
     * @return the server http response decorator
     */
    private ServerHttpResponseDecorator getServerHttpResponseDecorator(ServerWebExchange exchange,
                                                                       AtomicReference<String> requestBody) {
        // 获取response的返回数据
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        HttpStatus httpStatus = originalResponse.getStatusCode();
        ServerHttpRequest request = exchange.getRequest();
        URI requestUri = request.getURI();
        String uriQuery = requestUri.getQuery();
        String uri = requestUri.getPath() + (StringUtils.isNotBlank(uriQuery) ? "?" + uriQuery : "");
        HttpHeaders headers = request.getHeaders();
        String method = request.getMethodValue().toUpperCase();
        // 封装返回体
        return new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        DataBuffer join = bufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        DataBufferUtils.release(join);
                        Charset charset = RequestLogHelper.getMediaTypeCharset(originalResponse.getHeaders().getContentType());
                        String responseBody =content.length==0?"": new String(content, charset);
                        long handleTime = RequestLogHelper.getHandleTime(headers);
                        String trackerId = headers.getFirst(CommonConstants.AFS_TRACKER_ID);
                        MDC.put(AFS_TRACKER_ID, StringUtils.isEmpty(trackerId)? UUID.fastUUID().toString().replaceAll("-",""):trackerId);
                        LogUtils.setSystemName(environment);
                        if("dev".equals(environment.getActiveProfiles()[0])) {
                            log.info("\t请求Ip:{}\t请求uri:{}\tmethod:{}\t请求头:{}\t请求内容:{}\t响应内容:{}\thttp返回状态:{}\t处理时间:{}ms",
                                    IpUtils.getClientIp(request),
                                    uri, method, headers, requestBody.get() == null ? "" : requestBody.get(), responseBody, httpStatus,
                                    handleTime);
                        }else {
                            log.info("\t请求Ip:{}\t请求uri:{}\tmethod:{}\t请求头:{}}\thttp返回状态:{}\t处理时间:{}ms",
                                IpUtils.getClientIp(request), uri, method, headers, httpStatus, handleTime);
                        }
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body);
            }
        };
    }
}
